package XPathProj;

import java.util.AbstractMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;

import org.w3c.dom.Node;

/**
 * Class implements the NodeList interface.
 * Each element in the list is a node and the path that results
 * in this node. 
 * This list is mutable- enables adding nodes from the node list
 */
public class XpathNodeListImplementation extends XpathProjectNodeList {

	private List<Entry<Node,String>> nodeList; // holds the nodes
	
	/**
	 * Constructor- creates a new empty node list
	 */
	public XpathNodeListImplementation() {
		
		// allocate a new list to hold the nodes and their paths
		nodeList = new LinkedList<Entry<Node,String>>();
	}
	
	/**
	 * Constructor- creates a new node list, that is
	 * a (deep) copy of given list
	 * @param listToCopy- list to be copied
	 */
	public XpathNodeListImplementation(XpathNodeListImplementation listToCopy) {
		
		// allocate a new list to hold the nodes and their paths
		nodeList = new LinkedList<Entry<Node,String>>(listToCopy.nodeList);
	}
	
	/**
	 * Constructor- creates a new node list with a single element-
	 * given node and an empty string as a path
	 * @param node- node to initialize the list with
	 */
	public XpathNodeListImplementation(Node node) {
		
		// allocate a new list to hold the nodes and their paths
		nodeList = new LinkedList<Entry<Node,String>>();
		
		// add given node to list
		addNode(node);
	}
	
	/**
	 * Returns list's lengths (= number of nodes in list)
	 */
	@Override
	public int getLength() {
		
		// return node list's size
		return nodeList.size();
	}

	/**
	 * Returns item at given index.
	 * If given index exceeds list's length, null is returned 
	 */
	@Override
	public Node item(int index) {

		// check boundaries
		if (index < 0 || index >= nodeList.size()) {
			
			// given index exceeds list's boundaries
			return null;
		}
		
		// return element matching given index
		return nodeList.get(index).getKey();
	}

	/**
	 * Adds given node to the list
	 * @param newNode- node to add
	 * @return True if added the node, False if not
	 */
	public boolean addNode(Entry<Node,String> newNode) {

		// avoid duplicate nodes
		if (containsNode(newNode.getKey())) {
			
			// cannot add node
			return false;
		}
		
		// add given node
		return nodeList.add(newNode);
	}
	
	/**
	 * If given node is not contained in the node-list, adds to the node-list.
	 * The path for the new node will be an empty string.
	 * @param newNode- node to be added
	 */
	public void addNode(Node newNode) {
		
		// avoid duplicate nodes
		if (containsNode(newNode)) {
			
			// cannot add node
			return;
		}
		
		// wrap given node in an entry and add to list
		nodeList.add(new AbstractMap.SimpleEntry<Node, String>(newNode, ""));
	}
	
	/**
	 * Adds all nodes (and their paths) to the node-list.
	 * Adds only the nodes that are not already contained in list
	 * @param listToAdd- list to be added
	 */
	public void addNodeList(XpathNodeListImplementation listToAdd) {
		
		// scan given list and avoid duplicate nodes
		for (Entry<Node,String> currNewNode : listToAdd.nodeList) {
			
			// check if current node in given list is in this.nodeList
			if (containsNode(currNewNode.getKey())) {
				
				// do not add duplicate node
				continue;
			}
			
			// add current node
			nodeList.add(currNewNode);
		}
	}
	
	/**
	 * Removes given node from the list
	 * @param nodeToRemove- node to be removed from the list
	 * @return True if could add the node, False if not
	 */
	public boolean removeNode(Node nodeToRemove) {
		
		// remove specified node
		return nodeList.remove(nodeToRemove);
	}
    
	/**
	 * Returns list holding paths of all nodes
	 * @return list holding the paths
	 */
	public List<String> getPathList() {
		
		// initialize list to hold the paths
		List<String> paths = new LinkedList<String>();
		
		// scan node list and add paths
		int length = nodeList.size();
		for (int i = 0 ; i < length ; ++i) {
			
			// add current path
			paths.add(nodeList.get(i).getValue());
		}
		
		// return paths found
		return paths;
	}
	
	/**
	 * Returns path of index'th item
	 */
	public String itemPath(int index) {
    	 
		// return string attached to index'th node
		return nodeList.get(index).getValue();
	}
    
    /**
     * Checks if node-list contains given node 
     * @param node- node to check
     * @return True if contains, False if not
     */
	public boolean containsNode(Node node) {
		
		// scan nodes and check if given node appears in list
		for (Entry<Node,String> currNode : nodeList) {
			
			// check for an equal node
			if(node.isSameNode(currNode.getKey())) {
				
				// found match
				return true;
			}
		}
		
		// no node matches given node
		return false;
	}
	
	/**
	 * Clears nodes (and their paths) from list 
	 */
	public void clear() {
		
		// clear data
		nodeList.clear();
	}
	
	/**
	 * Sets index'th item's path to given path.
	 * If given index exceeds list, does nothing
	 * @param newPath- new path to be set for the item
	 * @param index- index of the item to set the new path
	 */
	public void setItemPath(String newPath, int index) {
		
		// backup index'th node
		Node nodeBackup = item(index);
		
		// check index boundaries
		if (nodeBackup == null) {
			
			// no element at given position, do nothing
			return;
		}
		
		// remove index'th element from list
		nodeList.remove(index);
		
		// add node with new path
		nodeList.add(index, new AbstractMap.SimpleEntry<Node, String>(nodeBackup, newPath));
	}
}